﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using JpLabs.Extensions;

namespace DistributedGame
{
    public class GameClient : IDisposable
    {
		public IGameHost GameHost { get; private set; }
		public IClientChannel GameChannel { get; private set; }
		
		public CommunicationState State
		{
			get {
				var channel = GameChannel;
				if (channel == null) return CommunicationState.Created;
				return channel.State;
			}
		}

		public event EventHandler<ValueChangedEventArgs<CommunicationState>> StateChanged;

		public ClientConnectionInfo Connect(Uri host, string playerName, IGameClientCallback clientInterface)
		//public IAsyncResult BeginConnect(Uri host, string playerName, IGameClientCallback clientInterface)
		{
			var context = new InstanceContext(clientInterface);
			
			var tcpBinding = new NetTcpBinding(SecurityMode.None, true);
			tcpBinding.ReliableSession.Enabled = true;
			
			tcpBinding.OpenTimeout = TimeSpan.FromSeconds(4);
			tcpBinding.CloseTimeout = TimeSpan.FromSeconds(2);
			tcpBinding.SendTimeout = TimeSpan.FromSeconds(4);
			tcpBinding.ReceiveTimeout = TimeSpan.MaxValue;
			tcpBinding.ReliableSession.InactivityTimeout = TimeSpan.FromSeconds(8);
			tcpBinding.ReliableSession.Ordered = true;
			
			var endPointAddress = new EndpointAddress(host.ToString());
			
			var endpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IGameHost)), tcpBinding, endPointAddress);
			
			var channelFactory = new DuplexChannelFactory<IGameHost>(context, endpoint);
			
			GameHost = channelFactory.CreateChannel();			
			GameChannel = GameHost as IClientChannel;

			new CommObjWatcher(GameChannel).StateChanged += GameChannel_StateChanged;
			
			GameChannel.Open();
			
			return this.GameHost.Connect(new ClientConnectRequest() { Name = playerName });
			//return this.GameHost.BeginConnect(new ClientConnectRequest() { Name = playerName });
			
			//return this.GameHost.EndConnect(async);
			
			//GameHost.Say("hello");
			
			//var ugh = gameHost as IDisposable;
			//ugh.Dispose(); //May throw exception based on inactivity timeout
		}

		public void Disconnect()
		{
			this.Dispose();
		}

		private void GameChannel_StateChanged(object sender, ValueChangedEventArgs<CommunicationState> e)
		{
			this.StateChanged.RaiseEvent(sender, e);
		}

		//public PlayerInfo EndConnect(IAsyncResult asyncResult)
		//{
		//    return this.GameHost.EndConnect(asyncResult);
		//}

		public void Dispose()
		{
			var host = this.GameHost;
			var channel = this.GameChannel;

			if (channel != null) {
				var debug = channel.State;
				
				if (channel.State == CommunicationState.Opened) {
					try
					{
						if (host != null) host.Disconnect();
						channel.Close();
					} catch {
						//Dangerously, ignore exceptions here
						if (System.Diagnostics.Debugger.IsAttached) throw;
					}
				}

				if (channel.State != CommunicationState.Faulted) channel.Dispose();
			}

			this.GameHost = null;
			this.GameChannel = null;
		}
	}
}
